package grammatica;

import java.util.ArrayList;


public class Rule {
	private NonTerminal identifier;
	private ArrayList<Definition> definitions;
	private Definition currentDefinition = null; 
	
	public Rule(String nonTerminal){
		identifier = new NonTerminal( nonTerminal );
		definitions = new ArrayList<Definition>();
	}

	public String getDefinitionName() {
		return identifier.getName();
	}
	
	public NonTerminal getIdentifier(){
		return identifier;
	}
	
	public void define(Term term){
		if(currentDefinition == null){
			currentDefinition = new Definition();
		}
		currentDefinition.addTerm(term);
	}
	
	public void finishDefinition(){
		if(currentDefinition != null)definitions.add(currentDefinition);
		currentDefinition = null;
	}

	public String printDefinitions() {
		String s = "";
		for(Definition d: definitions){
			if(!s.equals(""))s += " | ";
			s += d.print();
		}
		return s;
	}
	
	public boolean checkNonTerminals(ArrayList<NonTerminal> definedNonTerminals){
		boolean errorFound = false;
		for(Definition d : definitions){
			errorFound = d.checkNonTerminals(definedNonTerminals) || errorFound;
		}
		if(errorFound)System.out.println("Error found in definition of: " + getDefinitionName());
		return(errorFound);
	}
	
	public boolean canBeEmpty(){
		for(Definition d : definitions){
			if(d.isAnEmpty()) return true;
		}
		return false;
	}
	
	public ArrayList<Starter> getStarters(Grammatica grammatica){
		ArrayList<Starter> starters = new ArrayList<Starter>();
		ArrayList<NonTerminal> nonTermsToSkip = new ArrayList<NonTerminal>();
		nonTermsToSkip.add(identifier);
		getStarters(grammatica, starters, nonTermsToSkip, false);
		return starters;		
	}
	
	public ArrayList<Starter> getStartersRecursively(Grammatica grammatica){
		ArrayList<Starter> starters = new ArrayList<Starter>();
		ArrayList<NonTerminal> nonTermsToSkip = new ArrayList<NonTerminal>();
		nonTermsToSkip.add(identifier);
		getStarters(grammatica, starters, nonTermsToSkip, true);
		return starters;		
	}
	
	public boolean checkStarterMultiplicity(ArrayList<Starter> starters, boolean print){
		boolean multiples = false;
		for(int i = 0; i < starters.size(); i++){
			int j = starters.lastIndexOf(starters.get(i));
			if( j >= 0 && j != i){
				if(print)System.out.println("Error : " + starters.get(i) + " occurs more than once, an instance has been removed");
				starters.remove(i);
				i--;
				multiples = true;
			}
		}
		return multiples;
	}
	
	private ArrayList<Starter> getStarters(Grammatica grammatica,  ArrayList<Starter> starters, ArrayList<NonTerminal> nonTermsToSkip, boolean recursively){
		
		nonTermsToSkip.add(identifier);
		for(Definition d : definitions){
			if(!d.isAnEmpty()){
				getStartersFromDefinition(d, starters, grammatica, 0);
			}
		}
		if(recursively){
			boolean nonTermsFiltered = false;
			while(!nonTermsFiltered){
				NonTerminal nt = null;
				Starter st = null;
				for(Starter s : starters){//find non-terminals
					if(s.isNonTerminalStarter()){
						nt = (NonTerminal)s.getFirst();
						st = s;
						break;
					}
				}
				if(nt == null)nonTermsFiltered = true;
				else{
					if(!nonTermsToSkip.contains(nt)){
						Rule r = grammatica.getMatchingRule(nt);
						nonTermsToSkip.add(nt);
						if(r != null)r.getStarters(grammatica, starters, nonTermsToSkip, recursively);
					}
					starters.remove(st);
				}
			}
		}		
		return starters;
	}

	private void getStartersFromDefinition(Definition d,
			ArrayList<Starter> starters, Grammatica grammatica, int offset) {
		Term t = d.get(offset);
		if(t != null){
			Starter s = new Starter(t);
			starters.add(s);
			if(t instanceof NonTerminal){
				NonTerminal nt = (NonTerminal)t;
				Rule rr = grammatica.getMatchingRule(nt);
				if(rr!= null && rr.canBeEmpty()){
					getStartersFromDefinition(d, starters, grammatica, offset+1);
				}
			}
			else if(t instanceof Terminal){
				int i = offset+1;
				Term tt = d.get(i);
				while(tt instanceof Terminal){
					s.append(tt);
					i++;
					tt = d.get(i);
				}
			}
		}		
	}

	public void printStarters(Grammatica grammatica) {

		ArrayList<Starter> starters = getStarters(grammatica);
		checkStarterMultiplicity(starters, true);
		for(Starter s : starters){
			System.out.println(s);
		}
	}
	
	public ArrayList<Follower> getFollowers(Grammatica grammatica){
		ArrayList<Follower> followers = new ArrayList<Follower>();
		ArrayList<NonTerminal> nonTermsToSkip = new ArrayList<NonTerminal>();
		nonTermsToSkip.add(identifier);
		getFollowers(grammatica, followers, nonTermsToSkip);
		return followers;
	}

	private void getFollowers(Grammatica grammatica,
			ArrayList<Follower> followers,
			ArrayList<NonTerminal> nonTermsToSkip) {
		
		
		for(Rule r : grammatica.getRules()){
			if(!nonTermsToSkip.contains(r.identifier) && r.contains(identifier)){
				r.getMyFollowers(identifier, grammatica, followers, nonTermsToSkip);
			}
		}
		if(!followers.isEmpty()){
			Follower f;
			for(int i = 0; i < followers.size(); i++){
				f = followers.get(i);
				if(f.isNonTerminalFollower()){
					Rule r = grammatica.getMatchingRule((NonTerminal)f.getFirst());
					if(r != null){
						ArrayList<Starter> starters = r.getStartersRecursively(grammatica);
						checkStarterMultiplicity(starters, false);
						for(Starter s : starters){
							followers.add(new Follower(s.getFirst()));
						}
					}
					followers.remove(f);
					i--;
				}
				
			}
		}
		
		
	}

	private void getMyFollowers(NonTerminal findId, Grammatica grammatica,
			ArrayList<Follower> followers, ArrayList<NonTerminal> nonTermsToSkip) {
		nonTermsToSkip.add(identifier);
		boolean addMyFollowers = false;
		for(Definition d : definitions){
			if(d.contains(findId)){
				Term t = d.getNext(findId);
				if(t == null)addMyFollowers = true;
				else followers.add(new Follower(t));
			}
		}
		if(canBeEmpty() || addMyFollowers)getFollowers(grammatica, followers, nonTermsToSkip);
		
	}

	private boolean contains(NonTerminal nt) {
		for(Definition d : definitions){
			if( d.contains(nt))return true;
		}
		return false;
	}

	public ArrayList<Definition> getDefinitions(){
		return definitions;
	}

	public void printFollowers(Grammatica grammatica) {
		ArrayList<Follower> followers = getFollowers(grammatica);
		for(Follower f : followers){
			System.out.println(f);
		}		
	}
}
